package kouda.viewer;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import javax.swing.*;       
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

/**
 * File-based article viewer using Swing GUI; editing added later
 * 
 * 13/10/17 started
 * 13/10/18 Post button
 * 13/10/18 Update button
 * 13/10/21 Edit and New buttons added; no more Post button
 * 13/10/22 change title on double click
 * 13/10/24 posted on code.google
 * 13/10/31 delete
 * 13/11/05 rewrote directory search/sort code - not fast enough yet
 * 13/11/08 cache file info for possbile speeding up
 * 
 * @author kouda x kumi
 * @since  2013/10/17
 */
public class Viewer implements ListSelectionListener, ActionListener, Comparator<Article>{

    private DefaultListModel<Article> data = null;
    
    private JList<Article> list;
    private JTextArea textArea;

    private JButton editButton;
    private JButton newButton;
    private JButton deleteButton;
    private JButton updateButton;

    private JFrame frame;
    
    private void createAndShowGUI() {
        
      //Create the menu bar.  Make it have a green background.
//        JMenuBar menuBar = new JMenuBar();
//        menuBar.setOpaque(true);
//        menuBar.setBackground(new Color(154, 165, 127));
//        menuBar.setPreferredSize(new Dimension(200, 20));

        // list
        list = new JList<Article>(); //data has type Object[]
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setLayoutOrientation(JList.VERTICAL);
        list.setVisibleRowCount(12);
        list.addListSelectionListener(this);
        list.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent evt) {
                // JList<Article> list = (JList<Article>)evt.getSource();
                if (evt.getClickCount() == 2) { // double-click
                    int index = list.locationToIndex(evt.getPoint());
                    changeTitle(index);
                }
            }
        });        
        JScrollPane listScroller = new JScrollPane(list);
        
        // text area
        textArea = new JTextArea(20, 60);
        textArea.setEditable(false);
        textArea.setLineWrap(true);
        textArea.setMargin(new Insets(10, 10, 10, 10));

        JScrollPane textScroller = new JScrollPane(textArea); 
        
        // main panel
        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
        mainPanel.add(listScroller);
        mainPanel.add(textScroller);

        // edit/done button
        editButton = new JButton("Edit");
        editButton.setActionCommand("edit");
        editButton.addActionListener(this);
        editButton.setEnabled(false); // no selection in the list
        
        // new button
        newButton = new JButton("New");
        newButton.setActionCommand("new");
        newButton.addActionListener(this);

        // delete button
        deleteButton = new JButton("Delete");
        deleteButton.setActionCommand("delete");
        deleteButton.addActionListener(this);
        deleteButton.setEnabled(false);

        // update button
        updateButton = new JButton("Update");
        updateButton.setActionCommand("update");
        updateButton.addActionListener(this);
        
        // bottom panel
        JPanel bottomPanel = new JPanel();
        bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
        bottomPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        bottomPanel.add(Box.createHorizontalGlue());
        bottomPanel.add(editButton);
        bottomPanel.add(Box.createRigidArea(new Dimension(10, 0)));
        bottomPanel.add(newButton);
        bottomPanel.add(Box.createRigidArea(new Dimension(10, 0)));
        bottomPanel.add(deleteButton);
        bottomPanel.add(Box.createRigidArea(new Dimension(10, 0)));
        bottomPanel.add(updateButton);
        
        //Display the window.
        //Create and set up the window.
        frame = new JFrame("Article Viewer");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Set the menu bar and add the label to the content pane.
   //     frame.setJMenuBar(menuBar);
        frame.getContentPane().add(mainPanel, BorderLayout.CENTER);
        frame.getContentPane().add(bottomPanel, BorderLayout.SOUTH);
        
        frame.setMinimumSize(new Dimension(400, 400));
        frame.pack();
        frame.setVisible(true);

        // frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        // frame.getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

        // populate list data after the window appears
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                populateList();
                // frame.setCursor(Cursor.getDefaultCursor());
            }
        });

    }

    public void actionPerformed(ActionEvent e)
    {
        if ("edit".equals(e.getActionCommand())){
            System.out.println("edit button action");
            if ("Edit".equals(editButton.getText())) editText();
            else saveText(); // "Done"
        }
        else if ("new".equals(e.getActionCommand())){
            System.out.println("new button action");
            createEntry();
        }
        else if ("delete".equals(e.getActionCommand())){
            System.out.println("delete button action");
            deleteEntry();
        }
        else if ("update".equals(e.getActionCommand())){
            System.out.println("update button action");
            updateList();
            
            // throw new IllegalStateException("Uncaught Exception");
        }
    }

    private void updateList()
    {
        // list.clearSelection(); // done by setModel()
        textArea.setText("");
        editButton.setEnabled(false);
        deleteButton.setEnabled(false);
        
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                populateList();
            }
        });
    }

    private String getUsername()
    {
        String userName = System.getProperty("user.name");
        System.out.println("userNmae = " + userName);
        return userName;
    }
    
    private String inputTitle(String previous)
    {
        String input = JOptionPane.showInputDialog(null, "Type title (file name) without '.txt':", previous);
        System.out.println("input = " + input);
        input = replaceInvalidChars(input);
        return input;
    }

    private void createEntry()
    {
        String title = inputTitle(null);
        if (title == null) return;
        
        String author = getUsername();
        File dir = new File(parentFolder, "messages/" + author);
        dir.mkdirs(); // create the folder now
        
        Article a = new Article(dir, title, author);
        data.add(0,  a); // at top
        list.setSelectedIndex(0); // point the top one

        textArea.setText("Type text here and press [Done] when finished ....");
        textArea.setSelectionStart(0);
        textArea.setSelectionEnd(textArea.getText().length());

        editText();
    }

    private void changeTitle(int index)
    {
        System.out.println("change title");
        
        Article a = data.get(index);
        String previous = a.title;
        String newTitle = inputTitle(previous);
        if (newTitle == null || previous.equals(newTitle)) return;
        
        try {
            a.moveTo(newTitle);
        } catch (IOException e) {
            handleError(e);
        }
        
        data.set(index, a); // this results in a call to data.fireContentsChanged(data, index, index)
    }
    
    private String replaceInvalidChars(String s)
    {
        s = s.replaceAll("[\\\\/:*?\"<>:|]","_");
        
//        s = s.replace('\\','_');
//        s = s.replace('/','_');
//        s = s.replace(':','_');
//        s = s.replace('*','_');
//        s = s.replace('?','_');
//        s = s.replace('\"','_');
//        s = s.replace('<','_');
//        s = s.replace('>','_');
//        s = s.replace('|','_');

        return s;
    }
    
    private void deleteEntry()
    {
        int index = list.getSelectedIndex();
        if (index == -1) return; // for safety

        Article a = data.get(index);

        if (JOptionPane.showConfirmDialog(null, a.title, "Delete this file?", JOptionPane.OK_CANCEL_OPTION)
                != JOptionPane.OK_OPTION) return;

        data.remove(index);
        editButton.setEnabled(false); // removing an entry loses selection
        deleteButton.setEnabled(false);
        textArea.setText("");
        
        try {
            a.delete();
        } catch (IOException e) {
            handleError(e);
        }
    }

    private void editText()
    {
        if (list.getSelectedIndex() == -1) return; // for safety
        textArea.setEditable(true);
        editButton.setText("Done");
        
        updateButton.setEnabled(false);
        newButton.setEnabled(false);
        list.setEnabled(false);
        
        textArea.requestFocusInWindow();
    }
    
    private void saveText()
    {
        int index = list.getSelectedIndex();
        if (index == -1) return; // for safety

        // TEMPORARY: change LF only to CR/LF
        String text = textArea.getText();
        text = text.replace("\r", "");
        text = text.replace("\n", "\r\n");

        Article a = data.get(index);
        a.text = text;
        try {
            a.save();
        } catch (IOException e) {
            handleError(e);
        }
        
        data.set(index, a); // lastModified has　been updated

        textArea.setEditable(false);
        editButton.setText("Edit");
        
        updateButton.setEnabled(true);
        newButton.setEnabled(true);
        list.setEnabled(true);
    }
    
    public void valueChanged(ListSelectionEvent e) {
        if (e.getValueIsAdjusting() == false) {

            int index = list.getSelectedIndex();
            if (index == -1) return;
            
            Article a = data.get(index);
            try {
                a.load();
            } catch (IOException e1) {
                handleError(e1);
            }
            textArea.setText(a.text);
            textArea.setSelectionStart(0);
            textArea.setSelectionEnd(0);
            
            editButton.setEnabled(true);
            deleteButton.setEnabled(true);
        }
    }

    private void populateList() // called for init and update
    {
        if (data == null) data = new DefaultListModel<Article>();
        else data.clear();
        
        try {
            loadArticles();
        } catch (IOException e) {
            handleError(e);
        }
        
        // if (data == null || data.size() == 0) throw new IllegalStateException("No articles");
        
        list.setModel(data);
    }

    private String parentFolder = "."; // default
    private String articleFolder = "messages";
    
//    private void loadArticles() throws IOException
//    {
//        File folder = new File(parentFolder, articleFolder);
//        if (!folder.exists()) folder.mkdirs();
//        Collection<File> c = FileUtils.listFiles(folder, new String[] { "txt" }, true);
//        List<File> files = new ArrayList<File>(c); 
//        Collections.sort(files, this);
//        
//        for (File f : files)
//        {
//            Article a = new Article(f);
//            a.load();
//            data.addElement(a);
//        }
//    }

    private void loadArticles() throws IOException
    {
        long beforeList = System.currentTimeMillis();

        File folder = new File(parentFolder, articleFolder);
        if (!folder.exists()) folder.mkdirs();
        Collection<File> c = FileUtils.listFiles(folder, new String[] { "txt" }, true);
        
        long afterList = System.currentTimeMillis();
        
        List<Article> articles = new ArrayList<Article>();
        
        for (File f : c)
        {
            Article a = new Article(f);
            articles.add(a);
        }
        
        long beforeSort = System.currentTimeMillis();
        
        Collections.sort(articles, this);
        for (Article a : articles) data.addElement(a); // TODO: try directly sort data !
        
        long afterSort = System.currentTimeMillis();
        
        log("loadArticles: list = " + ((afterList - beforeList) / 1000.0) +
                ", build = " + ((beforeSort - afterList) / 1000.0) +
                ", sort = " + ((afterSort - beforeSort) / 1000.0) + 
                ", total = " + ((afterSort - beforeList) / 1000.0) + 
                " sec for " + data.size() + " files");
    }

    public int compare(Article a, Article b)
    {
        return (int)(b.lastModified - a.lastModified);
    }

    private void handleError(Throwable t)
    {
        t.printStackTrace();
        showError(t.getMessage());
    }

    private void showError(String msg)
    {
        JOptionPane.showMessageDialog(null, msg, "Error", JOptionPane.ERROR_MESSAGE); 
    }
    
    private void initViewer()
    {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm");
        log("\r\ninitViewer: " + getUsername() + ", " + sdf.format(new Date()));
        
        // setup for uncaught exception - java 7 only
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                handleError(e);
            }
        });
        
        try
        {
            createAndShowGUI();
            
            // throw new Exception("Exception while init");
        }
        catch (Exception e)
        {
            handleError(e);
        }
    }
    
    private String logFilename = "viewer.log";
    private Writer writer;

    private void log(String s)
    {
        try {
            writer = new FileWriter(new File(parentFolder, logFilename), true);
            writer.append(s + "\r\n");
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private void run(String[] args)
    {
        if (args.length > 0) parentFolder = args[0];
        
        // test - running DOS dir and get output - not working yet
//        Process p;
//        try {
//            p = Runtime.getRuntime().exec("cmd /c dir messages /s /b");
//            List<String> files = IOUtils.readLines(p.getInputStream(), "SJIS");
//            int n = files.size();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }

        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                initViewer();
            }
        });
    }
    
    public static void main(String[] args) {
     
//        System.setProperty("java.security.policy", "./viewer.policy");
        // same as: java -Djava.security.manager -Djava.security.policy=<policy-file> <run-class>
        // default policy file: <java-home>/lib/security/java.policy
        // policy file must be in UTF-8.
        
//        if (System.getSecurityManager() == null) {
//                    System.setSecurityManager(new SecurityManager());
//                    // same as: java -Djava.security.manager <run-class>
//        }

        (new Viewer()).run(args);
    }
}
